Linguaggio C

 

per collaborazioni, commenti, critiche, e altro contattateci alla e-mail: clubinfo@libero.it risponderemo al più presto!

Fondamenti del linguaggio

di Luca Sabatucci

Lezione 3

Pagina principale | Lezione precedente | Lezione successiva


Fondamenti del linguaggio C

Ogni linguaggi si contraddistingue per le proprie parole chiave, operatori e sintassi. In questa lezione si ressumono i concetti fondamentali che poi andremo trattando in seguito.
Questa lezione sarà un riferimento costante per tutte le lezioni successive. Cercherò quindi di renderla quanto più comoda e leggibile possibile mediante l'uso delle tabelle e dei colori.

Parole chiave

La sintassi del C non prevede tantissime parole chiave, soprattutto se paragonate a linguaggi ad alto livello come il Visual Basic.
Vediamo queste parole chiave riassunte nella tabella 1.1.

Tabella 1.1: parole chiave del linguaggio C
auto break case char const continue default do
double else enum extern float for goto if
int long register return short signed sizeof static
struct switch typedef union unsigned void volatile while

Operatori

La sintassi invece è molto ricca di operatori che vediamo riassunti nella tabella 1.2:

Tabella 1.2: operatori del linguaggio C
operatori di assegnamento
= += -= *= /=

operatori aritmetici
- + * / % -- ++
operatori relazionali
> < >= <= == !=
operatori logici
&& || !



operatori per bit
& | ^ - >> <<
operatori per i puntatori
* & ->



altri operatori
? : sizeof . , [] ()

Operatori di assegnamento

L'operatore di assegamento può essere usato all'interno di ogni tipo di espressione a differenza di molti altri linguaggi in cui l'assegnamento viene considerato un comando.
La sintassi tipica è:

nome_variabile = espressione

Sebbene a destra del segno uguale si possa trovare una qualunque espressione, è obbligatorio che a sinistra si trovi una variabile.
Sono anche ammessi assegnamenti multipli:


X = Y = Z = 0;

Nell'esempio le tre variabili X,Y e Z assumono tutte le stesso valore 0. L'operazione di assegnamento infatti restituisce un valore, ovvero il valore assegnato alla variabile. La stessa espressione, può essere vista così:


X = (Y = (Z = 0));

In questa forma si vede che solo a Z viene assegnato 0, a Y viene assegnato il valore dato dall'espressione (Z = 0), e così via anche per X. Questo concetto, che non è essenziale nella programmazione, deve comunque essere chiaro, perché come vedremo in seguito una istruzione di questo tipo:


if (X=0) ...

è presente un errore che solo un occhio allenato individua. Si tratta dell'uso errato dell'operatore di confronto che non è =, bensì è ==. L'operazione effettuata dentro le parentesi in questo caso è (incredibile!) una assegnazione. Poiché l'assegnazione restituisce valore 0, le istruzioni non vengono mai eseguite. Sebbene il C permetta di effettuare assegnazioni in ogni punto del listato, questo risulta essere un pessimo modo di programmare, che porta a listati di difficile comprensione.

Una nota va fatta per gli altri due operatori += e -=. L'operazione effettuata è quella di una somma o una differenza con il valore assunto dall'espressione. Ad esempio:


X = 10;
X += 2;

la variabile X assume valore 12.

Operatori aritmetici

Sugli operatori aritmetici c'è ben poco da dire, in quanto si usano come in tutti i linguaggi e sono: somma, sottrazione, moltiplicazione, divisione e modulo; vediamo una tipica espressione aritmetica:


(5+(3*X)/Y)-2

Come vedremo ogni operatore ha un determinato livello di precedenza; ad esempio * e / hanno precedenza maggiore di + e -; questo vuol dire che 2-3*5 da come risultato 2-(3*5)=-13 e non (2-3)*5=-5. Poiché è difficile ricordarsi sempre la scala delle precedenze conviene fare uso intensivo delle parentesi, che hanno la funzione di aumentare la precedenza dell'espressione contenuta. In questo modo si è certi del risultato ottenuto.
L'operatore di modulo %, che potrebbe non essere molto conosciuto, effettua una divisione intera tra i due operandi e restituisce il resto; ad esempio 12 % 5 = 2, perché 12/5 = 10, con resto 2.

L'unica nota di riguardo di questa sezione sono gli operatori ++ e --, difficilmente riscontrabili in altri linguaggi. Si tratta degli operatori di incremento e decremento.


X = 10;
Y = 1;
X++;
Y--;

L'operatore di incremento aggiunge 1 al risultato, ed è equivalente all'istruzione X = X+1;. L'operatore di decremento sottrae 1 al risultato, ed è equivalente all'istruzione Y = Y-1;. Alla fine delle operazioni, X vale 11 e Y vale 0.

Si osservi che tali operatori assumono sfumature di significato diverse a secondo della loro posizione rispetto alla variabile; si osservino queste due istruzioni:


Y++;
++Y;

sono identiche, in questo contesto, ma poiché abbiamo detto che una espressione può essere usata in un contesto più complesso si osservino adesso queste due:


Y = 10;
if (Y++ == 10) ...
if (++Y == 10) ...

in questo caso si cela una differenza. Nel caso Y++, Y viene prima confrontata con 10 e poi incrementata. Nell'altro caso avviene l'opposto. Quindi solo il primo dei due confronti ha successo.

Operatori relazionali e logici

Con operatori relazionali si intende degli operatori che permettono di effettuare confronti tra due espressioni. Con gli operatori logici si possono interconnettere più operatori relazionali. Poiché l'istruzione per eccellenza che effettua dei controlli sulle espressioni è if, faremo i nostri esempi con questa parola chiave. Il significato dell'istruzione:


if (espressione booleana)
	istruzione da eseguire;

è la seguente: se il valore tra parentesi risulta vero (maggiore o uguale a zero) allora l'istruzione che segue viene eseguita, altrimenti se risulta falso (minore di 0) l'istruzione non viene eseguita. Gli operatori relazionali infatti restituiscono proprio un valore che è 0 se l'espressione risulta veritiera e -1 se risulta falsa.

Un tipico uso di operatori relazionali è


if (X >= 0)
	istruzione da eseguire;
if (X != 20)
	istruzione da eseguire;
if (X == 5)
	istruzione da eseguire;

Rispettivamente l'istruzione viene eseguita se X è maggiore o uguale a zero (numero non negativo), se X è diverso da 20, se vale esattamente 5.

Certe volte risulta necessario effettuare più confronti sulla stessa variabile, ad esempio si può volere che X sia positiva ma non esattamente pari a 4. Questo può essere fatto usando come istruzione da eseguire proprio un'altra istruzione if:


if (X > 0)
	if (X != 4)
		istruzione da eseguire;

Ma un metodo più comodo e ordinato per eseguire questo confronto è quello di ricorrere agli operatori logici:


if (X > 0 && X != 4)
	istruzione da eseguire;

Gli operatori logici sono && (AND), || (OR) e ! (NOT). Normalmente questi operatori si studiano mediante una tabella di verità, in cui si esauriscono tutti i possibili casi. Ci basti dire che l'operatore AND restituisce vero quando entrambi gli operandi sono veri, l'operatore OR restituisce vero quando uno dei due operandi è vero, mentre l'operatore NOT converte vero in falso e viceversa.

Operatori per puntatori

Tralasciando ad uno studio più avanzato la trattazione degli operatori per bit, vediamo quali sono gli operatori per puntatori. Abbiamo già detto che un puntatore è una variabile che contiene un indirizzo di memoria e abbiamo già detto quali sono i problemi a cui si va incontro. Tuttavia in C i puntatori hanno un ruolo molto importante ed è giusto cominciare a vedere quali sono le operazioni possibili.
L' operatore unario & (cioè che coinvolge un solo operatore) restituisce l'indirizzo di memoria di una variabile, ovvero la posizione in cui questa è memorizzata:


&var

indica qual'è la posizione della variabile val nella memoria del computer. Conviene quindi leggere l'operatore & come "l'indirizzo di". Se p è un puntatore si può effettuare l'assegnamento:


p = &var

L'altro operatore è * che applicato ad un indirizzo di memoria fornisce il contenuto: *p è il valore della variabile val. Questo può essere usato come una espressione a tutti gli effetti.


var = 10;
p = &var;
(*p)++;

Nel codice val alla fine vale 11, perché il suo valore è stato incrementato direttamente in memoria. si osservi come l'ultima istruzione (*p)++; sia nettamente diversa da p++. La prima incrementa il valore della variabile puntata. la seconda invece dice al puntatore di puntare alla posizione di memoria immediatamente successiva. Nessuno però ci garantisce che tale posizione di memoria sia una variabile, oppure un programma, o altro...

L'ultimo operatore -> lo vedremo in seguito, quando affronteremo le strutture.

Operatore ?

L'operatore ? sostituisce il costrutto if ... then ... else, anche se rende un pò più complessa la lettura del listato. L'operatore ? è un operatore ternario, ovvero accetta tra operandi. La forma generale è:

espressione booleana ? espressione1 : espressione2

Ad esempio:


var = (X > 0) ? 10 : Z;

che equivale all'istruzione:


if (X > 0) 
	var = 10;
else
	var = Z;	

Come si vede il costrutto è molto sintetico (e da questo nasce quella difficoltà di lettura di cui parlavo) ma può essere molto comodo in determinate circostanze

Operatore sizeof

L'operatore sizeof è un operatore unario, cioè si applica ad un solo operando e restituisce la lunghezza in byte della variabile o di uno specificatore di tipo.
Quello che a prima vista sembra una cosa del tutto inutile vedremo che sarà fondamentale per la definizione di strutture dati dinamiche.

La sua forma è sizeof(variabile); vediamo alcuni esempi:


X = sizeof(var);
Y = sizeof(double);

Operatore virgola

La virgola ',' in C è un operatore, e il suo scopo è quello di permettere di concatenare più espressioni. Ad esempio:


X = (Y++, Y/Z);

L'operatore ammette due operandi. Solo quello di destra viene valutato come valore dell'espressione. Nell'esempio Y viene prima incrementata e poi divisa per Z. Ad X viene assegnato il valore di Y/Z.

Questi costrutti liberano la fantasia del programmatore, in quanto una stessa operazione può essere effettuata in svariati modi, ad esempio:


X = (Y++)/Z;

oppure


Y++;
X = Y/Z;

Ma ci si rende subito conto che tutta questa libertà è assolutamente non necessaria. Iniziando a programmare si assume uno stile, in cui ogni problema viene risolto sempre alla stessa maniera. In questo modo risulta più chiaro il listato e l'eventuale fase di comprensione.
Per quanto mi riguarda non uso praticamente mai costrutti in cui c'è l'operatore ? o l'operatore virgola, cerco sempre di suddividere una istruzione nelle sue componenti minime (terzo esempio) anche se questo aumenta il numero di righe del codice.

Precedenze tra operatori

Ogni operatore possiede un determinato livello di precedenza; una stessa espressione infatti potrebbe essere interpretata in modi diversi:


X = var * P / ++Y - (Z < 0) ? 10 : T*3;

potrebbe essere interpretato come:


X = var * P / ++(Y - ((Z < 0) ? 10 : (T*3)) );

oppure come:


X = var * (P / ++Y) - ((Z < 0) ? 10 : T) * 3);

L'ordine corretto di applicazione degli operatori è indicato nella tabella 1.3:

Tabella 1.3: precedenze degli operatori
Alta
() [] -> .
! ++ -- - * & sizeof
* / %
+ -
<< >>
< <= > >=
== !=
&
|
&&
||
?:
= += -= *= /=
Bassa

Personalmente non ricordo mai l'ordine di precedenza dei vari operatori, a parte quelli aritmetici, e cerco sempre di fare un discreto uso di parentesi. Le parentesi migliorano la comprensione dell'espressione. Se l'espressione con le parentesi è ancora troppo complessa conviene spezzarla in più espressioni ed eventualmente fare ricorso a variabili intermedie:


X = var * (P / ++Y) - ((Z < 0) ? 10 : T) * 3);

può essere scritto come:


temp1 = (Z < 0) ? 10 : T;
Y++;	
temp2 = P / Y;
X = (var * temp2) - (temp1 * 3); 

Quale delle due forme vi sembra più semplice da comprendere?

Nella prossima lezione parleremo delle variabili, di come si istanziano e di come si usano.
Arrivederci alla prossima!


Bibliografia


Testi consigliati per l'apprendimento

Questo articolo è stato scaricato dal Club di informatica
Pagina curata da Luca Sabatucci